﻿using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ShareHelper
{
    /// <summary>
    ///  Protobuf 数据解析
    /// </summary>
    public static class Protobuf
    {
        /// <summary>
        /// 转换到实体类
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string ToEntity(byte[] data)
        {
            if (Deserialize(data, out object value))
            {
                return Deserialize("Root", (Dictionary<long, object>)value); ;
            }
            else return string.Empty;
        }

        private static string Deserialize(string className, Dictionary<long, object> data)
        {
            StringBuilder str = new StringBuilder("[ProtoContract]\r\npublic class ");
            str.Append(className);
            str.Append("\r\n{");
            if (className.StartsWith("Root"))
                className = "Entity";
            foreach (var item in data)
            {
                bool IsRequired = false;
                string Entity, type;
                Entity = type = string.Empty;
                var aaaa = item.Value.GetType().Name;
                switch (item.Value.GetType().Name)
                {
                    case "ArrayList":
                        string name = $"{className}_{item.Key}";
                        Entity = GetList(ref name, item.Value);
                        type = $"List<{name}>";
                        break;

                    case "Dictionary`2":
                        type = $"{className}_{item.Key}";
                        Entity = Deserialize(type, (Dictionary<long, object>)item.Value);
                        break;

                    case "String":
                        IsRequired = string.IsNullOrEmpty((string)item.Value);
                        type = "string";
                        break;

                    case "Int64":
                        IsRequired = (long)item.Value == 0;
                        type = "long";
                        break;

                    case "Single":
                        IsRequired = (float)item.Value == 0;
                        type = "float";
                        break;

                    case "Double":
                        IsRequired = (double)item.Value == 0;
                        type = "double";
                        break;

                    default:
                        throw new ArgumentException($"ToEntity---未知类型-{aaaa}");
                }
                if (!string.IsNullOrEmpty(Entity))
                {
                    str.Insert(0, "\r\n");
                    str.Insert(0, Entity);
                }
                str.Append(GetDescribe(item.Key, type, IsRequired));
            }
            str.Append("}\r\n");
            return str.ToString();
        }

        private static string GetList(ref string name, object value)
        {
            string data = string.Empty;
            foreach (var item in (ArrayList)value)
            {
                string EntityName = item.GetType().Name;
                string Entity = string.Empty;
                switch (EntityName)
                {
                    case "ArrayList":
                        throw new ArgumentException("ToEntity---List--List--??");

                    case "Dictionary`2":
                        Entity = Deserialize(name, (Dictionary<long, object>)item);
                        break;

                    case "String":
                        name = "string";
                        break;

                    case "Int64":
                        name = "long";
                        break;

                    case "Single":
                        name = "float";
                        break;

                    case "Double":
                        name = "double";
                        break;

                    default:
                        throw new ArgumentException($"ToEntity---List--未知类型--{EntityName}");
                }
                if (string.IsNullOrEmpty(Entity))
                {
                    break;
                }
                else
                {
                    if (Entity.Length > data.Length)
                    {
                        data = Entity;
                    }
                }
            }

            return data;
        }

        private static string GetDescribe(long key, string type, bool IsRequired)
        {
            StringBuilder str = new StringBuilder("\r\n[ProtoMember(");
            str.Append(key);
            if (IsRequired)
                str.Append(",IsRequired =true");
            str.Append(")]\r\npublic ");
            str.Append(type);
            str.Append(" unk");
            str.Append(key);
            str.Append(";\r\n");
            return str.ToString();
        }

        /// <summary>
        /// 解析 protobuf 到JSON 字符串
        /// </summary>
        /// <param name="data">protobuf</param>
        /// <returns></returns>
        public static string ToJson(byte[] data)
        {
            if (data?.Length > 0 && Deserialize(data, out object value))
            {
                return JsonConvert.SerializeObject(value, Formatting.None);
            }
            else return string.Empty;
        }

        /// <summary>
        /// Protobuf 对象解析
        /// </summary>
        /// <param name="data">Protobuf 数据</param>
        /// <param name="protobuf">解析结果</param>
        /// <returns>解析成功 或 失败</returns>

        public static bool Deserialize(byte[] data, out object protobuf)
        {
            protobuf = null;
            int index = 0;//解析位置
            Dictionary<long, object> Dic = new Dictionary<long, object>();//保存对象
            while (data.Length > index)//判断 byte 长度 是否 大于解析位置
            {
                if (!GetLong(data, ref index, out long head))//获取key type
                {
                    return false;//获取key type 异常
                }
                long key = (int)(head >> 3);//获取key
                long type = (int)(head & 7);//获取 type
                object value;//解析内容保存
                if (1 > key) return false;//key 异常
                if (type == 0)
                {
                    if (GetLong(data, ref index, out long lvalue))
                    {
                        value = lvalue; //数值解析
                    }
                    else return false;//数值解析 异常
                }
                else if (type == 1)
                {
                    if (GeFixed64(data, ref index, out Double fixed64))
                    {
                        value = fixed64;
                    }
                    else return false;//Fixed64 异常
                }
                else if (type == 2)//字符串 对象 数组 列表 解析
                {
                    if (!GetObject(data, ref index, out value)) return false;
                }
                else if (type == 5)
                {
                    if (GeFixed32(data, ref index, out float fixed32)) value = fixed32;//Fixed32 解析
                    else return false;//Fixed32 异常
                }
                else return false;//type 解析错误
                if (Dic.ContainsKey(key))// 判断key 是否存在
                {
                    if (!(Dic[key] is ArrayList listObj))// 如已存在判断是否为列表
                    {
                        listObj = new ArrayList//不为列表 创建列表
                        {
                            Dic[key]//将之前的内容添加进去
                        };
                    }
                    listObj.Add(value);//是列表添加内容
                    Dic[key] = listObj;//;//为key 更新内容
                }
                else Dic.Add(key, value);//key 不存在 直接添加
            }
            protobuf = Dic.OrderBy(a => a.Key).ToDictionary(b => b.Key, c => c.Value);
            return true;// 返回解析出的 内容
        }

        /// <summary>
        /// Fixed-length 4-byte encoding
        /// </summary>
        /// <param name="bytes">protobuf</param>
        /// <param name="index">解析位置</param>
        /// <param name="value"> 解析出的数据</param>
        /// <returns> 成功 失败</returns>
        private static bool GeFixed32(byte[] bytes, ref int index, out float value)
        {
            value = 0;
            if (bytes.Length - index < 4) return false;
            value = BitConverter.ToSingle(bytes, index);
            index += 4;
            return true;
        }

        /// <summary>
        ///  Fixed-length 8-byte encoding
        /// </summary>
        /// <param name="bytes">protobuf</param>
        /// <param name="index">解析位置</param>
        /// <param name="value"> 解析出的数据</param>
        /// <returns> 成功 失败</returns>
        private static bool GeFixed64(byte[] bytes, ref int index, out Double value)
        {
            value = 0;
            if (bytes.Length - index < 8) return false;
            value = BitConverter.ToDouble(bytes, index);
            index += 8;
            return true;
        }

        /// <summary>
        /// 字符串 对象 数组 列表 解析
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private static bool GetObject(byte[] bytes, ref int index, out object value)
        {
            value = null;
            if (!GetLong(bytes, ref index, out long len)) return false;
            if (index + (int)len > bytes.Length) return false;//判断内容是否超长
            if (len == 0)
                value = string.Empty;
            else
            {
                byte[] data = new byte[len];//创建保存 byte [](字节集)
                Array.Copy(bytes, index, data, 0, data.Length);//拷贝 字节集
                index += (int)len;//更新解析位置
                if (!Deserialize(data, out value))//尝试解析到对象
                {
                    try //解析失败
                    {
                        value = Encoding.UTF8.GetString(data);//尝试解析到 string
                    }
                    catch (Exception)
                    {
                        value = data.ToList();// 解析到 list
                    }
                }
            }
            return true;
        }

        /// <summary>
        ///  解析到长征数型
        /// </summary>
        /// <param name="bytes">protobuf</param>
        /// <param name="index">解析位置</param>
        /// <param name="value"> 解析出的数据</param>
        /// <returns> 成功 失败</returns>
        private static bool GetLong(byte[] bytes, ref int index, out long value)
        {
            value = 0;
            string str;//
            StringBuilder strB = new StringBuilder();//保存变量
            while (bytes.Length > index && strB.Length < 64)
            {
                str = Convert.ToString(bytes[index++], 2);//byte 转换到 二进制文本
                if (str.Length == 8)
                    strB.Insert(0, str.Substring(1, 7)); //判断是否八位八位取后七位
                else//不够八位直接添加
                {
                    strB.Insert(0, str);
                    break;
                }
            }
            if (strB.Length > 64 || strB.Length == 0)
                return false;
            value = Convert.ToInt64(strB.ToString(), 2);
            return true;
        }
    }
}